package com.zds.demo.util;

import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.IntDef;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;

import org.json.JSONArray;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;

/**
 * 日志工具类
 * 初始化: {@link #init(int, String, ILogPersistence)} 若需要持久化, 请自行实现 {@link ILogPersistence} 接口
 */
@Keep
public final class LoggerUtil {
    private static final String TAG = "LoggerUtil";
    private static String sTag = "cus_logger";

    public static final int LEVEL_VERBOSE = 0;
    public static final int LEVEL_DEBUG = 1;
    public static final int LEVEL_INFO = 2;
    public static final int LEVEL_WARN = 3;
    public static final int LEVEL_ERROR = 4;
    public static final int LEVEL_NONE = 10;
    // 禁用日志持久化功能
    private static boolean disablePersistenceLog = true;


    @IntDef({LEVEL_VERBOSE, LEVEL_DEBUG, LEVEL_INFO, LEVEL_WARN, LEVEL_ERROR, LEVEL_NONE})
    public @interface LoggerLevel {

    }

    private static final int JSON_INDENT = 2;
    private static final int MIN_STACK_OFFSET = 3;

    @LoggerLevel
    private static int mLogLevel = LEVEL_DEBUG; // 有效的log等级,低于此级别,不打印
    @LoggerLevel
    private static int cacheLogLevel = mLogLevel; // 用于禁用启用logcat时进行恢复
    private static ILogPersistence logPersistenceImpl; // 日志持久化实现类

    // 初始化ILogPersistence前缓存的日志条数, 负数或0表示不不缓存
    private static int cacheSize = 100;
    @LoggerLevel
    private static int cacheLogMinLevel = mLogLevel; // 需要进行缓存的最低日志等级
    private static final List<LogMessage> cacheList = new ArrayList<>();

    private static class LogMessage {
        @LoggerLevel
        public int logLevel;
        public String tag;
        public String msg;
        public boolean keepFormat;

        public LogMessage(@LoggerLevel int logLevel, String tag, String msg, boolean keepFormat) {
            this.logLevel = logLevel;
            this.tag = tag;
            this.msg = msg;
            this.keepFormat = keepFormat;
        }
    }

    private LoggerUtil() {
    }

    /**
     * 在 logPersistenceImpl 初始化前, 允许缓存的日志条数
     * 负数或0表示不缓存, 默认缓存100条
     */
    public static void updateLogCacheSize(int maxSize, @LoggerLevel int level) {
        cacheSize = maxSize;
        cacheLogMinLevel = level;
    }

    /**
     * @param level          有效的log等级,低于此级别,不打印
     * @param tag            默认的tag信息
     * @param logPersistence 持久化工具实现类
     */
    public static void init(@LoggerLevel int level, String tag, @Nullable ILogPersistence logPersistence) {
        mLogLevel = level;
        cacheLogLevel = level;
        sTag = tag;
        logPersistenceImpl = logPersistence;
        if (logPersistenceImpl != null && !cacheList.isEmpty()) {
            Log.d(TAG, "init: 当前有缓存的日志, 进行写入");
            for (LogMessage item : cacheList) {
                filterPersistenceLog(item.logLevel, item.tag, item.msg);
            }
            cacheList.clear();
        }
    }

    /**
     * 是否启用logcat, 默认true
     *
     * @param enableLogcat      是否允许logcat输出, 若为false,则 logcat和 demolog 均不会输出
     * @param enablePersistence 是否允许写入demolog日志文件
     */
    public static void updateSetting(boolean enableLogcat, boolean enablePersistence) {
        mLogLevel = enableLogcat ? cacheLogLevel : LoggerUtil.LEVEL_NONE;
        disablePersistenceLog = !enablePersistence;

        // 输出一条日志,方便排查
        Log.w(TAG, "updateSetting enableLogcat=" + enableLogcat
                + ",logLevel=" + mLogLevel
                + ",disablePersistenceLog=" + disablePersistenceLog
        );
    }

    public static void v(String msg) {
        v(sTag, msg);
    }

    public static void d(String msg) {
        d(sTag, msg);
    }

    public static void i(String msg) {
        i(sTag, msg);
    }

    public static void w(String msg) {
        w(sTag, msg);
    }

    public static void e(String msg) {
        e(sTag, msg);
    }

    public static void v(String tag, String msg) {
        if (LEVEL_VERBOSE >= mLogLevel && !TextUtils.isEmpty(msg)) {
            String s = getMethodNames();
            Log.v(tag, String.format(s, msg));
            filterPersistenceLog(LEVEL_VERBOSE, tag, msg);
        }
    }

    public static void d(String tag, String msg) {
        if (LEVEL_DEBUG >= mLogLevel && !TextUtils.isEmpty(msg)) {
            String s = getMethodNames();
            Log.d(tag, String.format(s, msg));
            filterPersistenceLog(LEVEL_DEBUG, tag, msg);
        }
    }

    public static void i(String tag, String msg) {
        if (LEVEL_INFO >= mLogLevel && !TextUtils.isEmpty(msg)) {
            String s = getMethodNames();
            Log.i(tag, String.format(s, msg));
            filterPersistenceLog(LEVEL_INFO, tag, msg);
        }
    }

    public static void w(String tag, String msg) {
        w(tag, msg, false);
    }

    public static void w(String tag, String msg, boolean keepFormat) {
        if (LEVEL_WARN >= mLogLevel && !TextUtils.isEmpty(msg)) {
            String s = getMethodNames();
            Log.w(tag, String.format(s, msg));
            filterPersistenceLog(LEVEL_WARN, tag, msg, keepFormat);
        }
    }

    public static void e(String tag, String msg) {
        e(tag, msg, false);
    }

    public static void e(String tag, String msg, boolean keepFormat) {
        if (LEVEL_ERROR >= mLogLevel && !TextUtils.isEmpty(msg)) {
            String s = getMethodNames();
            Log.e(tag, String.format(s, msg));
            filterPersistenceLog(LEVEL_ERROR, tag, msg, keepFormat);
        }
    }

    /**
     * 不输出logcat,直接写入日志
     */
    public static void writeLog(String tag, String msg) {
        writeLog(tag, msg, false);
    }

    public static void writeLog(String tag, String msg, boolean keepFormat) {
        if (!TextUtils.isEmpty(msg)) {
            filterPersistenceLog(LEVEL_WARN, tag, msg, keepFormat);
        }
    }

    private static void filterPersistenceLog(@LoggerLevel int logLevel, String tag, String msg) {
        filterPersistenceLog(logLevel, tag, msg, false);
    }

    /**
     * 判断日志是否需要持久化
     */
    private static void filterPersistenceLog(@LoggerLevel int logLevel, String tag, String msg, boolean keepFormat) {
        if (disablePersistenceLog) {
            return;
        }

        if (logPersistenceImpl != null) {
            logPersistenceImpl.filterPersistenceLog(logLevel, tag, msg, keepFormat);
        } else if (cacheSize > 0 && logLevel >= cacheLogMinLevel) {
            synchronized (LoggerUtil.class) {
                cacheList.add(new LogMessage(logLevel, tag, msg, keepFormat));
                int size = cacheList.size();
                if (size >= cacheSize) {
                    cacheList.remove(0);
                }
            }
        }
    }

    public static void flush() {
        if (logPersistenceImpl != null) {
            logPersistenceImpl.flush();
        }
    }

    /**
     * 打印json格式化字符串,在log过滤条中使用关键字 "system.out" 来搜索查找
     *
     * @param tag 当打印或解析出错时,打印日志用
     */
    public static void json(String tag, String json) {
        if (TextUtils.isEmpty(json)) {
            d("Empty/Null json content", tag);
            return;
        }

        try {
            json = json.trim();
            if (json.startsWith("{")) {
                JSONObject jsonObject = new JSONObject(json);
                String message = jsonObject.toString(JSON_INDENT);
                message = message.replaceAll("\n", "\n║ ");
                String s = getMethodNames();
                System.out.printf(s, message);
            } else if (json.startsWith("[")) {
                JSONArray jsonArray = new JSONArray(json);
                String message = jsonArray.toString(JSON_INDENT);
                message = message.replaceAll("\n", "\n║ ");
                String s = getMethodNames();
                System.out.printf(s, message);

            }
        } catch (Exception e) {
            e.printStackTrace();
            e("Invalid Json", tag);
        }
    }


    /**
     * 获取程序执行的线程名,类名和方法名,以及行号等信息
     */
    private static String getMethodNames() {
        // 福银偶现anr, 日志指向 getStackTrace 语句, 此处加下 logcat 用于后续排查
        // Log.d(TAG, "getMethodNames start");
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        int stackOffset = getStackOffset(stackTrace);
        stackOffset++;
        String sb = stackTrace[stackOffset].getMethodName() +
                "(" + stackTrace[stackOffset].getFileName() +
                ":" + stackTrace[stackOffset].getLineNumber() +
                ") " + "%s";
        // Log.d(TAG, "getMethodNames end");
        return sb;
    }

    private static int getStackOffset(StackTraceElement... trace) {
        int i = MIN_STACK_OFFSET;
        while (i < trace.length) {
            String name = trace[i].getClassName();
            if (!LoggerUtil.class.getName().equalsIgnoreCase(name)) {
                return --i;
            }
            i++;
        }
        return -1;
    }
}